package zmiddleware

import (
	"net/http"

	metav1 "gitee.com/youkelike/ziam/zapi/meta/v1"
	"gitee.com/youkelike/ziam/zcode/zcode"
	"gitee.com/youkelike/ziam/zerrors"
	"gitee.com/youkelike/ziam/zorm/store"
	"gitee.com/youkelike/ziam/zutils/response"
	"github.com/gin-gonic/gin"
)

func Validation() gin.HandlerFunc {
	return func(c *gin.Context) {
		if err := isAdmin(c); err != nil {
			switch c.FullPath() {
			case "/v1/users":
				if c.Request.Method != http.MethodPost {
					response.Write(c, zerrors.WithCode(zcode.ErrPermissionDenied, ""), nil)
					c.Abort()
					return
				}
			case "/v1/users/:name", "/v1/user/:name/change_password":
				username := c.GetString("username")
				if c.Request.Method == http.MethodDelete || (c.Request.Method != http.MethodDelete && username != c.Param("name")) {
					response.Write(c, zerrors.WithCode(zcode.ErrPermissionDenied, ""), nil)
					c.Abort()
					return
				}
			default:
			}
		}
		c.Next()
	}
}

func isAdmin(c *gin.Context) error {
	username := c.GetString(UsernameKey)

	// 这里必须用 Client() 方法，否则会因为 GetMysqlFactoryOr() 方法参数用到 zoptions，导致循环引用，即
	// 如果这样用：
	// factory, _ := mysql.GetMysqlFactoryOr()
	// user, err := factory.Users().Get(c, username, metav1.GetOptions{})
	// 就会导致如下的循环引用：
	// imports gitee.com/youkelike/ziam/zoptions/options
	// imports gitee.com/youkelike/ziam/zserver
	// imports gitee.com/youkelike/ziam/zmiddleware
	// imports gitee.com/youkelike/ziam/zorm/store/mysql
	// imports gitee.com/youkelike/ziam/zoptions/options
	// 如果改成调用  Client() 方法，引用就变成了这样：
	// imports gitee.com/youkelike/ziam/zoptions/options
	// imports gitee.com/youkelike/ziam/zserver
	// imports gitee.com/youkelike/ziam/zmiddleware
	// imports gitee.com/youkelike/ziam/zorm/store
	// 从而消除了循环引用
	// 由此可知，模块之间整体是可以循环 import 的，只要模块内的包之间不涉及循环引用就行

	// 这里的 store.Client() 可以调用的前提是，已经在其它地方注入了 Facotry 实例
	user, err := store.Client().Users().Get(c, username, metav1.GetOptions{})
	if err != nil {
		return err
	}

	if user.IsAdmin != 1 {
		return zerrors.WithCode(zcode.ErrPermissionDenied, "user %s is not a administrator", username)
	}

	return nil
}
